﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Cinch;
using CodeStash.Common.Enums;
using System.Reflection;
using CodeStash.Common.DTOS;
using MEFedMVVM.ViewModelLocator;
using System.ComponentModel.Composition;
using CodeStash.Addin.Core.Interfaces;
using CodeStash.Addin.Resources;
using System.Windows.Input;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Threading;
using CodeStash.Addin.Helpers;
using Microsoft.VisualStudio.PlatformUI;
using CodeStash.Addin.Core;

namespace CodeStash.Addin.ViewModels
{
    [ExportViewModel("SearchViewModel")]
    [PartCreationPolicy(CreationPolicy.NonShared)]
    public class SearchViewModel : ViewModelBase
    {
        #region Data
        private SearchType selectedSearchType;
        private IViewAwareStatus viewAwareStatusService;
        private IMessageBoxService messageBoxService;
        private IRestService restService;
        private IStatusBarService statusBarService;
        private JSONLanguage selectedLanguage;
        private CodeSnippetVisibility selectedVisibility;
        private string keyWord;
        private string tags;
        private bool isValid = false;
        private int searchTimeOutMilliSeconds = 60000;
        private CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();

        //AsyncHost stuff
        private string waitText;
        private string errorMessage;
        private AsyncType asyncState = AsyncType.Content;

        //rating stuff
        private bool includeRatings = true;
        private List<int> ratings=new List<int>();
        private int rating;
        #endregion

        #region Ctor
        [ImportingConstructor]
        public SearchViewModel(
            IViewAwareStatus viewAwareStatusService, 
            IMessageBoxService messageBoxService,
            IRestService restService,
            IStatusBarService statusBarService,
            ILoginDetails loginDetails)
        {
            this.viewAwareStatusService = viewAwareStatusService;
            this.messageBoxService = messageBoxService;
            this.restService = restService;
            this.restService.SetLoginDetails(loginDetails.Load());
            this.statusBarService = statusBarService;

            this.restService.SetLoginDetails(loginDetails.Load());

            Mediator.Instance.Register(this);

            RetrieveLanguages();
            SetVisibilities();
            selectedSearchType = SearchType.ByLanguage;
            this.viewAwareStatusService.ViewLoaded += ViewAwareStatusService_ViewLoaded;

            Validator = new SearchViewModelValidator(this);

            SearchCommand = new SimpleCommand<object, object>(ExecuteSearchCommand);

            for (int i = 1; i <= 5; i++)
            {
                ratings.Add(i);
            }
            rating = 1;
        }
        #endregion

        #region Private Methods

        private void ViewAwareStatusService_ViewLoaded()
        {
            SelectedVisibility = Visibilities.First();
            //TODO : Make this select the one that is the same file name
            //as current editor window file type if possible
            SelectedLanguage = Languages.First();
        }


        /// <summary>
        /// Get the languages using the RESTful service. Rather than caching the
        /// languages and reusing it, we need to reget the languages just in case
        /// the list has been updated on the server so that the user doesn't have
        /// a stale list.
        /// </summary>
        private void RetrieveLanguages()
        {
            statusBarService.SetText(Messages.GetLanguage);
            JSONLanguagesResult languages = restService.RetrieveLanguages();
            Languages = languages.Languages;
            statusBarService.Clear();
        }

        private void SetVisibilities()
        {
            List<CodeSnippetVisibility> vises = new List<CodeSnippetVisibility>();
            vises.Add(CodeSnippetVisibility.JustMe);
            vises.Add(CodeSnippetVisibility.MyTeam);
            vises.Add(CodeSnippetVisibility.Public);
            Visibilities = vises;

        }


        private void DoSearch(int pageNumber, bool fetchingDueToPaging)
        {
            AsyncState = AsyncType.Busy;
            WaitText = "Searching for snippets";
            ApplicationHelper.DoEvents();



            CancellationToken cancellationToken = cancellationTokenSource.Token;
            //Use a neat little trick which uses Delayed task to cancel the search task after a certain
            //time. This is better than using searchTask.Wait(timeOut) on the searchTask as that would 
            //block the UI thread which is obviously very bad thing. This approach is much better, no 
            //blocking of UI thread occurs
            Task<bool> cancellationDelayTask = TaskHelper.CreateDelayTask(searchTimeOutMilliSeconds);
            cancellationDelayTask.ContinueWith(dt =>
            {
                cancellationTokenSource.Cancel();
            }, TaskContinuationOptions.OnlyOnRanToCompletion);


            try
            {
                //Create cancellable TPL task, that can be cancelled after 'searchTimeOutMilliSeconds' using the
                //cancellationDelayTask above. Which as is better than using 
                Task<JSONPagesSearchResultCodeSnippet> searchTask = Task.Factory.StartNew<JSONPagesSearchResultCodeSnippet>(() =>
                {
                    string searchText = GetSearchText();
                    string[] tagsArray = !String.IsNullOrEmpty(this.Tags) && this.SelectedSearchType == SearchType.ByTag
                                                ? this.Tags.Split(new string[] { ";", ",", ":" }, StringSplitOptions.RemoveEmptyEntries)
                                                : new string[] { };

                    if ((!string.IsNullOrEmpty(searchText) && this.SelectedSearchType != SearchType.ByTag) ||
                        (tagsArray.Any() && this.SelectedSearchType == SearchType.ByTag))
                    {
                        JSONPagesSearchResultCodeSnippet results = restService.Search(
                                                                    searchText,
                                                                    this.SelectedSearchType,
                                                                    this.SelectedVisibility,
                                                                    tagsArray,
                                                                    pageNumber,
                                                                    includeRatings,
                                                                    rating);
                        return results;
                    }
                    else
                    {
                        return null;
                    }

                }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);

                //Notify SearchResultsViewModel of results on sucess
                searchTask.ContinueWith(ant =>
                {
                    if (!fetchingDueToPaging)
                    {
                        Mediator.Instance.NotifyColleagues<JSONPagesSearchResultCodeSnippet>("DisplaySearchResults", ant.Result);
                    }
                    else
                    {
                        Mediator.Instance.NotifyColleagues<JSONPagesSearchResultCodeSnippet>("DisplayPagedSearchResults", ant.Result);
                    }
                    AsyncState = AsyncType.Content;
                    ApplicationHelper.DoEvents();
                }, cancellationToken, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.FromCurrentSynchronizationContext());


                //Show error in SearchView
                searchTask.ContinueWith(ant =>
                {
                    AsyncState = AsyncType.Error;
                    ErrorMessage = "Search failed to run to completion";
                }, cancellationToken, TaskContinuationOptions.NotOnRanToCompletion, TaskScheduler.FromCurrentSynchronizationContext());
            }
            catch 
            {
                ErrorMessage = "Search failed to run correctly";
                AsyncState = AsyncType.Error;
                ApplicationHelper.DoEvents();
            }
        }


        private string GetSearchText()
        {
            switch (this.SelectedSearchType)
            {
                case SearchType.ByKeyWord:
                    return this.KeyWord;
                case SearchType.ByLanguage:
                    return this.SelectedLanguage.Language;
                default:
                    return "";
            }
        }
        #endregion

        #region Public Properties

        /// <summary>
        /// SearchCommand
        /// </summary>
        public ICommand SearchCommand { get; private set; }

        /// <summary>
        /// Gets of sets the Validator
        /// </summary>
        public SearchViewModelValidator Validator { get; private set; }

        /// <summary>
        /// Gets of sets the Visibilities
        /// </summary>
        public List<CodeSnippetVisibility> Visibilities { get; private set; }

         /// <summary>
        /// Gets of sets the Languages
        /// </summary>
        public List<JSONLanguage> Languages { get; private set; }

        /// <summary>
        /// Returns true if the KeyWord is required for search
        /// </summary>
        static PropertyChangedEventArgs isKeyWordRequiredArgs =
            ObservableHelper.CreateArgs<SearchViewModel>(x => x.IsKeyWordRequired);
        
        public bool IsKeyWordRequired
        {
            get { return selectedSearchType == SearchType.ByKeyWord; }
        }

        /// <summary>
        /// Returns true if a Language is required for search
        /// </summary>
        static PropertyChangedEventArgs isLanguageRequiredArgs =
            ObservableHelper.CreateArgs<SearchViewModel>(x => x.IsLanguageRequired);


        public bool IsLanguageRequired
        {
            get { return selectedSearchType == SearchType.ByLanguage; }
        }

        /// <summary>
        /// Gets or sets the SelectedLanguage
        /// </summary>
        static PropertyChangedEventArgs selectedLanguageArgs =
            ObservableHelper.CreateArgs<SearchViewModel>(x => x.SelectedLanguage);

        public JSONLanguage SelectedLanguage
        {
            get
            {
                return selectedLanguage;
            }
            set
            {
                if (selectedLanguage == value) return;
                selectedLanguage = value;
                NotifyPropertyChanged(selectedLanguageArgs);
                isValid = Validator.Validate();
            }
        }


        /// <summary>
        /// Gets or sets the SelectedVisibility
        /// </summary>
        static PropertyChangedEventArgs selectedVisibilityArgs =
            ObservableHelper.CreateArgs<SearchViewModel>(x => x.SelectedVisibility);

        public CodeSnippetVisibility SelectedVisibility
        {
            get
            {
                return selectedVisibility;
            }
            set
            {
                if (selectedVisibility == value) return;
                selectedVisibility = value;
                NotifyPropertyChanged(selectedVisibilityArgs);
                isValid = Validator.Validate();
            }
        }


        /// <summary>
        /// Gets or sets the KeyWord
        /// </summary>
        static PropertyChangedEventArgs keyWordArgs =
            ObservableHelper.CreateArgs<SearchViewModel>(x => x.KeyWord);

        public string KeyWord
        {
            get { return keyWord; }
            set
            {
                if (keyWord == value) return;
                keyWord = value;
                NotifyPropertyChanged(keyWordArgs);
                isValid = Validator.Validate();
            }
        }


        /// <summary>
        /// Gets or sets the Tags
        /// </summary>
        static PropertyChangedEventArgs tagsArgs =
            ObservableHelper.CreateArgs<SearchViewModel>(x => x.Tags);
        
        public string Tags
        {
            get { return tags; }
            set
            {
                if (tags == value) return;
                tags = value;
                NotifyPropertyChanged(tagsArgs);
                isValid = Validator.Validate();
            }
        }


        /// <summary>
        /// Gets or sets the SelectedSearchType
        /// </summary>
        static PropertyChangedEventArgs selectedSearchTypeArgs =
            ObservableHelper.CreateArgs<SearchViewModel>(x => x.SelectedSearchType);

        public SearchType SelectedSearchType
        {
            get { return selectedSearchType; }
            set
            {
                selectedSearchType = value;
                NotifyPropertyChanged(selectedSearchTypeArgs);
                NotifyPropertyChanged(isKeyWordRequiredArgs);
                NotifyPropertyChanged(isLanguageRequiredArgs);
                isValid = Validator.Validate();
            }
        }


        /// <summary>
        /// Gets or sets the AsyncState
        /// </summary>
        static PropertyChangedEventArgs asyncStateArgs =
            ObservableHelper.CreateArgs<SearchViewModel>(x => x.AsyncState);

        public AsyncType AsyncState
        {
            get { return asyncState; }
            private set
            {
                asyncState = value;
                NotifyPropertyChanged(asyncStateArgs);
            }
        }


        /// <summary>
        /// Gets or sets the WaitText
        /// </summary>
        static PropertyChangedEventArgs waitTextArgs =
            ObservableHelper.CreateArgs<SearchViewModel>(x => x.WaitText);

        public string WaitText
        {
            get { return waitText; }
            private set
            {
                waitText = value;
                NotifyPropertyChanged(waitTextArgs);
            }
        }


        /// <summary>
        /// Gets or sets the ErrorMessage
        /// </summary>
        static PropertyChangedEventArgs errorMessageArgs =
            ObservableHelper.CreateArgs<SearchViewModel>(x => x.ErrorMessage);

        public string ErrorMessage
        {
            get { return errorMessage; }
            private set
            {
                errorMessage = value;
                NotifyPropertyChanged(errorMessageArgs);
            }
        }

        /// <summary>
        /// Gets or sets the IncludeRatings
        /// </summary>
        static PropertyChangedEventArgs includeRatingsArgs =
            ObservableHelper.CreateArgs<SearchViewModel>(x => x.IncludeRatings);

        public bool IncludeRatings
        {
            get { return includeRatings; }
            set
            {
                includeRatings = value;
                NotifyPropertyChanged(includeRatingsArgs);
            }
        }

        /// <summary>
        /// Gets the possible ratings values
        /// </summary>
        public List<int> Ratings
        {
            get { return ratings; }
        }

        /// <summary>
        /// Gets or sets the Ratings
        /// </summary>
        static PropertyChangedEventArgs ratingArgs =
            ObservableHelper.CreateArgs<SearchViewModel>(x => x.Rating);

        public int Rating
        {
            get { return rating; }
            set
            {
                rating = value;
                NotifyPropertyChanged(ratingArgs);
            }
        }
        #endregion

        #region Mediator Messages
        [MediatorMessageSink("FetchPageOfSearchResults")]
        private void OnFetchPageOfSearchResults(int currentPage)
        {
            if (AsyncState != AsyncType.Busy)
            {
                DoSearch(currentPage, true);
            }
        }

        [MediatorMessageSink("CloseSearchViewPopup")]
        private void OnCloseSearchViewPopup(bool dummy)
        {
            ((DialogWindow)viewAwareStatusService.View).Close();
        }
        #endregion

        #region Command Handlers

        #region Search Command
        private void ExecuteSearchCommand(object parameter)
        {
            if (isValid)
            {
                DoSearch(1, false);
            }
            else
            {
                messageBoxService.ShowError("Your search is invalid, please rectify and try again");
            }
        }

        #endregion
        #endregion
    }

}
